/*
 * This file is part of Zql.
 *
 * Zql is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Zql is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Zql.  If not, see <http://www.gnu.org/licenses/>.
 */

package br.com.iqt.zql;

import java.util.* ;

/**
 * ZExpression: an SQL Expression
 * An SQL expression is an operator and one or more operands
 * Example: a AND b AND c -> operator = AND, operands = (a, b, c)
 */
public class ZExpression implements ZExp{
    
    String op_ = null;
    Vector operands_ = null;
    int type_= ZExpression.SIMPLE_EXPRESSION;
    
    public static int SIMPLE_EXPRESSION = 1;//Expressão
    public static int FUNCTION = 2;//Função
    public static int TYPE = 3;//Tipo

    public int getType() {
        return type_;
    }

    public void setType(int type) {
        this.type_ = type;
    }
    
    public void setOperator(String op) {
        this.op_ = op;
    }
    
    public void setOperands(Vector operands) {
        this.operands_ = operands;
    }
    
    /**
     * Create an SQL Expression given the operator
     * @param op The operator
     */
    public ZExpression(String op) {
        op_ = new String(op);
    }
    
    /**
     * Create an SQL Expression given the operator and 1st operand
     * @param op The operator
     * @param o1 The 1st operand
     */
    public ZExpression(String op, ZExp o1) {
        op_ = new String(op);
        addOperand(o1);
    }
    
    /**
     * Create an SQL Expression given the operator, 1st and 2nd operands
     * @param op The operator
     * @param o1 The 1st operand
     * @param o2 The 2nd operand
     */
    public ZExpression(String op, ZExp o1, ZExp o2) {
        op_ = new String(op);
        addOperand(o1);
        addOperand(o2);
    }
    
    /**
     * Get this expression's operator.
     * @return the operator.
     */
    public String getOperator() { return op_; }
    
    
    
    /**
     * Get this expression's operands.
     * @return the operands (as a Vector of ZExp objects).
     */
    public Vector getOperands() {
        return operands_;
    }
    
    /**
     * Add an operand to the current expression.
     * @param o The operand to add.
     */
    public void addOperand(ZExp o) {
        if(operands_ == null) operands_ = new Vector();
        operands_.addElement(o);
    }
    
    /**
     * Get an operand according to its index (position).
     * @param pos The operand index, starting at 0.
     * @return The operand at the specified index, null if out of bounds.
     */
    public ZExp getOperand(int pos) {
        if(operands_ == null || pos >= operands_.size()) return null;
        return (ZExp)operands_.elementAt(pos);
    }
    
    /**
     * Get the number of operands
     * @return The number of operands
     */
    public int nbOperands() {
        if(operands_ == null) return 0;
        return operands_.size();
    }
    
    /**
     * String form of the current expression (reverse polish notation).
     * Example: a > 1 AND b = 2 -> (AND (> a 1) (= b 2))
     * @return The current expression in reverse polish notation (a String)
     */
    public String toReversePolish() {
        StringBuffer buf = new StringBuffer("(");
        buf.append(op_);
        for(int i = 0; i < nbOperands(); i++) {
            ZExp opr = getOperand(i);
            if(opr instanceof ZExpression)
                buf.append(" " + ((ZExpression)opr).toReversePolish()); // Warning recursive call
            else if(opr instanceof ZQuery)
                buf.append(" (" + opr.toString() + ")");
            else
                buf.append(" " + opr.toString());
        }
        buf.append(")");
        return buf.toString();
    }
    
    public String toString() {
        
        if(op_.equals("?")) return op_; // For prepared columns ("?")
        
        if(type_ == ZExpression.FUNCTION)
            return formatFunction();
        
        if(type_ == ZExpression.TYPE)
            return op_;

        StringBuffer buf = new StringBuffer();
        if(needPar(op_)) buf.append("(");
        
        ZExp operand;
        switch(nbOperands()) {
            
            case 1:
                operand = getOperand(0);
                if(operand instanceof ZConstant) {
                    // Operator may be an aggregate function (MAX, SUM...)
                    if(ZUtils.isAggregate(op_))
                        buf.append(op_ + "(" + operand.toString() + ")");
                    else if(op_.equals("IS NULL") || op_.equals("IS NOT NULL"))
                        buf.append(operand.toString() + " " + op_);
                    // "," = list of values, here just one single value
                    else if(op_.equals(",")) buf.append(operand.toString());
                    else buf.append(op_ + " " + operand.toString());
                } else if(operand instanceof ZQuery) {
                    buf.append(op_ + " (" + operand.toString() + ")");
                } else {
                    if(op_.equals("IS NULL") || op_.equals("IS NOT NULL"))
                        buf.append(operand.toString() + " " + op_);
                    // "," = list of values, here just one single value
                    else if(op_.equals(",")) buf.append(operand.toString());
                    else buf.append(op_ + " " + operand.toString());
                }
                break;
                
            case 3:
                if(op_.toUpperCase().endsWith("BETWEEN")) {
                    buf.append(getOperand(0).toString() + " " + op_ + " "
                            + getOperand(1).toString()
                            + " AND " + getOperand(2).toString());
                    break;
                }
                
            default:
                
                boolean in_op = op_.equals("IN") || op_.equals("NOT IN");
                
                int nb = nbOperands();
                for(int i = 0; i < nb; i++) {
                    
                    if(in_op && i==1) buf.append(" " + op_ + " (");
                    
                    operand = getOperand(i);
                    if(operand instanceof ZQuery && !in_op) {
                        buf.append("(" + operand.toString() + ")");
                    } else {
                       // System.out.println(buf);
                        buf.append(operand.toString());
                    }
                    if(i < nb-1) {
                        if(op_.equals(",") || (in_op && i>0)) buf.append(", ");
                        else if(!in_op) buf.append(" " + op_ + " ");
                    }
                }
                if(in_op) buf.append(")");
                break;
        }
        
        if(needPar(op_)) buf.append(")");
        return buf.toString();
    }
    
    private boolean needPar(String op) {
        String tmp = op.toUpperCase();
        return ! (tmp.equals("ANY") || tmp.equals("SOME") || tmp.equals("ALL")
                || tmp.equals("UNION") || ZUtils.isAggregate(tmp));
    }
    
    private String formatFunction() {
        StringBuffer b = new StringBuffer(op_ + "(");
        int nb = nbOperands();
        for(int i = 0; i < nb; i++) {
            b.append(getOperand(i).toString() + (i < nb-1 ? "," : ""));
        }
        b.append(")");
        return b.toString();
    }
};

